package com.hnl.core.connection;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DBConnectionManager implements ConnectionManager {
	public static final Logger LOG = LoggerFactory
			.getLogger(DBConnectionManager.class);

	private Vector<Driver> drivers = new Vector<Driver>(); // drivers
	private Hashtable<String, DBConnectionPool> pools = new Hashtable<String, DBConnectionPool>(); // connection
	private int defaultMaxConnection;
	private int timeoutCheck;

	public DBConnectionManager(int defaultMaxConnection, int timeoutCheck) {
		this.defaultMaxConnection = defaultMaxConnection;
		this.timeoutCheck = timeoutCheck;
	}

	public Connection getConnection() {
		DBConnectionPool connPool = pools.elements().nextElement();
		return connPool.getConnection();
	}

	public void releaseConnection(Connection conn) {
		DBConnectionPool connPool = pools.elements().nextElement();
		connPool.freeConnection(conn);
	}

	public Connection getConnection(String DBName) {
		DBConnectionPool connPool = pools.get(DBName);
		return connPool.getConnection();
	}

	public void releaseConnection(String dbname, Connection conn) {
		DBConnectionPool connPool = pools.get(dbname);
		connPool.freeConnection(conn);
	}

	public void loadDrivers(String driverClassName) {
		try {
			Driver driver = (Driver) Class.forName(driverClassName)
					.newInstance();
			DriverManager.registerDriver(driver);
			drivers.addElement(driver);
			LOG.info("Successfully registered JDBC Driver: " + driverClassName);
		} catch (Exception e) {
			LOG.error("Unable to register JDBC Driver.", e);
		}
	}

	public void createPool(String name, String URL, String user,
			String password, int maxConn) {
		try {
			DBConnectionPool pool = new DBConnectionPool(name, URL, user,
					password, maxConn);
			pools.put(name, pool);

			LOG.info("Successfully created DB Pool");
		} catch (Exception e) {
			LOG.error("Unable to create DB Pool.", e);
		}
	}

	private class DBConnectionPool {
		private Vector<Connection> freeConnections = new Vector<Connection>();
		private int maxConn;
		private String name;
		private String password;
		private String URL;
		private String user;

		public DBConnectionPool(String name, String URL, String user,
				String password, int maxConn) {
			this.name = name;
			this.URL = URL;
			this.user = user;
			this.password = password;
			this.maxConn = maxConn;
		}

		public synchronized void freeConnection(Connection con) {
			freeConnections.addElement(con);
			notifyAll();
		}

		public synchronized Connection getConnection() {
			Connection conn = null;
			if (freeConnections.size() > 0) {
				conn = (Connection) freeConnections.firstElement();
				freeConnections.removeElementAt(0);
				try {
					if (!conn.isValid(timeoutCheck)) {
						conn.close();
						conn = getConnection();
					}
				} catch (SQLException e) {
					conn = getConnection();
				}
				if (freeConnections.size() > maxConn) {
					releaseOneConnection();
				}
			} else {
				conn = newConnection();
			}

			return conn;
		}

		public synchronized void releaseAll() {
			for (Connection con : freeConnections) {
				try {
					con.close();
				} catch (SQLException e) {
					LOG.error("Unable to close connection.", e);
				}
			}
			freeConnections.removeAllElements();
		}

		public synchronized void releaseOneConnection() {
			if (freeConnections.firstElement() != null) {
				Connection con = (Connection) freeConnections.firstElement();
				try {
					con.close();
				} catch (SQLException e) {
					LOG.error("Unable to close connection.", e);
				}
			}
			freeConnections.removeElementAt(0);
		}

		private Connection newConnection() {
			Connection conn = null;
			try {
				if (user == null) {
					conn = DriverManager.getConnection(URL);
				} else {
					conn = DriverManager.getConnection(URL, user, password);
				}
			} catch (SQLException e) {
				LOG.error("Unable to create a new Connection.", e);
				return null;
			}
			return conn;
		}
	}

	public int getDefaultMaxConnection() {
		return defaultMaxConnection;
	}

	public void setDefaultMaxConnection(int defaultMaxConnection) {
		this.defaultMaxConnection = defaultMaxConnection;
	}

	public int getTimeoutCheck() {
		return timeoutCheck;
	}

	public void setTimeoutCheck(int timeoutCheck) {
		this.timeoutCheck = timeoutCheck;
	}

}
